# Babel 插件

import { SourceCode } from 'rspress/theme';

<SourceCode href="https://github.com/web-infra-dev/rsbuild/tree/main/packages/plugin-babel" />

Rsbuild 默认使用 SWC 编译，当内置的功能无法满足诉求、需要添加一些 Babel presets 或 plugins 进行额外处理时，你可以使用 Rsbuild 的 Babel 插件。

## 快速开始

### 安装插件

你可以通过如下的命令安装插件:

import { PackageManagerTabs } from '@theme';

<PackageManagerTabs command="add @rsbuild/plugin-babel -D" />

### 注册插件

你可以在 `rsbuild.config.ts` 文件中注册插件：

```ts title="rsbuild.config.ts"
import { pluginBabel } from '@rsbuild/plugin-babel';

export default {
  plugins: [pluginBabel()],
};
```

## 编译缓存

使用 Babel 插件后，Rsbuild 除了执行默认的 SWC 转译，还会执行 Babel 转译，存在额外的编译开销，这可能导致构建速度明显降低。

为了降低 Babel 转译的开销，`@rsbuild/plugin-babel` 默认开启了 Babel 编译缓存。如果你希望禁用缓存，可以将 `performance.buildCache` 设置为 `false`：

```ts title="rsbuild.config.ts"
export default {
  performance: {
    buildCache: false,
  },
};
```

## 选项

### babelLoaderOptions

传递给 `babel-loader` 的选项，请查阅 [babel-loader 文档](https://github.com/babel/babel-loader) 来了解具体用法。

- **类型：** `Object | Function`
- **默认值：**

```ts
const defaultOptions = {
  babelrc: false,
  compact: false,
  configFile: false,
  plugins: [
    ['@babel/plugin-proposal-decorators', config.source.decorators],
    ...(isLegacyDecorators ? ['@babel/plugin-transform-class-properties'] : []),
  ],
  presets: [
    [
      '@babel/preset-typescript',
      {
        allExtensions: true,
        allowDeclareFields: true,
        allowNamespaces: true,
        isTSX: true,
        optimizeConstEnums: true,
      },
    ],
  ],
};
```

#### Function 类型

当配置项为 Function 类型时，默认 Babel 配置会作为第一个参数传入，你可以直接修改配置对象，也可以返回一个对象作为最终的 `babel-loader` 配置。

```js
pluginBabel({
  babelLoaderOptions: (config) => {
    // 添加一个插件，比如配置某个组件库的按需引入
    config.plugins ||= [];
    config.plugins.push([
      'babel-plugin-import',
      {
        libraryName: 'my-components',
        libraryDirectory: 'es',
        style: true,
      },
    ]);
  },
});
```

函数的第二个参数提供了一些方便的工具函数，请继续阅读下方文档。

:::tip
以上示例仅作为参考，通常来说，你不需要手动配置 `babel-plugin-import`，因为 Rspack SWC 编译已支持 transformImport 能力，Rsbuild 也提供了更通用的 [source.transformImport](/config/source/transform-import) 配置。
:::

#### Object 类型

当配置项的值为 `Object` 类型时，会与默认配置通过 `Object.assign` 浅合并。

:::caution
`Object.assign` 是浅拷贝，会完全覆盖内置的 `presets` 或 `plugins` 数组，导致内置的 presets 或 plugins 失效，请在明确影响面的情况下再使用这种方式。
:::

```js
pluginBabel({
  babelLoaderOptions: {
    plugins: [
      [
        'babel-plugin-import',
        {
          libraryName: 'my-components',
          libraryDirectory: 'es',
          style: true,
        },
      ],
    ],
  },
});
```

#### 工具函数

配置项为 Function 类型时，第二个参数可用的工具函数如下:

##### addPlugins

- **类型：** `(plugins: BabelPlugin[]) => void`

添加若干个 Babel 插件。

```js
pluginBabel({
  babelLoaderOptions: (config, { addPlugins }) => {
    addPlugins([
      [
        'babel-plugin-import',
        {
          libraryName: 'my-components',
          libraryDirectory: 'es',
          style: true,
        },
      ],
    ]);
  },
});
```

##### addPresets

- **类型：** `(presets: BabelPlugin[]) => void`

添加若干个 Babel 预设配置 (大多数情况下不需要增加预设)。

```js
pluginBabel({
  babelLoaderOptions: (config, { addPresets }) => {
    addPresets(['@babel/preset-env']);
  },
});
```

##### removePlugins

- **类型：** `(plugins: string | string[]) => void`

移除 Babel 插件，传入需要移除的插件名称即可，你可以传入单个字符串，也可以传入一个字符串数组。

```js
pluginBabel({
  babelLoaderOptions: (config, { removePlugins }) => {
    removePlugins('babel-plugin-import');
  },
});
```

##### removePresets

- **类型：** `(presets: string | string[]) => void`

移除 Babel 预设配置，传入需要移除的预设名称即可，你可以传入单个字符串，也可以传入一个字符串数组。

```js
pluginBabel({
  babelLoaderOptions: (config, { removePresets }) => {
    removePresets('@babel/preset-env');
  },
});
```

### include

- **类型：** `string | RegExp | (string | RegExp)[]`
- **默认值：** `undefined`

用于指定需要 Babel 编译的文件。

由于 Babel 编译存在性能开销，通过 `include` 来匹配部分文件可以减少 Babel 编译的模块数量，从而提升构建性能。

比如，只对 `.custom.js` 文件进行编译，并忽略 `node_modules` 下的文件：

```js
pluginBabel({
  include: /\.custom\.js$/,
  // 建议 exclude node_modules 目录以提升构建性能
  exclude: /[\\/]node_modules[\\/]/,
});
```

:::tip
当你配置 `include` 或 `exclude` 选项时，Rsbuild 会创建一条单独的 Rspack rule 来应用 babel-loader 和 swc-loader。

这条单独的 rule 与 Rsbuild 内置的 SWC rule 是完全独立的，并且不会受到 [source.include](/config/source/include) 和 [source.exclude](/config/source/exclude) 的作用。
:::

### exclude

- **类型：** `string | RegExp | (string | RegExp)[]`
- **默认值：** `undefined`

用于指定不需要 Babel 编译的文件。

由于 Babel 编译存在性能开销，通过 `exclude` 来排除部分文件可以减少 Babel 编译的模块数量，从而提升构建性能。

## 调试配置

当你通过配置项修改 `babel-loader` 配置后，可以在 [Rsbuild 调试模式](/guide/debug/debug-mode) 下查看最终生成的配置。

首先通过 `DEBUG=rsbuild` 参数开启调试模式：

```bash
# 调试开发环境
DEBUG=rsbuild pnpm dev

# 调试生产环境
DEBUG=rsbuild pnpm build
```

然后打开生成的 `rspack.config.web.mjs`，搜索 `babel-loader` 关键词，即可看到完整的 `babel-loader` 配置内容。

## 常见问题

### 编译卡死

在使用 Babel 插件后，如果编译进度条卡死，但终端无 Error 日志时，通常是因为编译过程中出现了异常。在某些情况下，当 Error 被 webpack 或其他模块捕获后，错误日志不会被正确输出。最为常见的场景是 Babel 配置出现异常，抛出 Error 后被 webpack 捕获，而 webpack 在个别情况下吞掉了 Error。

**解决方法：**

如果你修改 Babel 配置后出现此问题，建议检查是否有以下错误用法：

1. 配置了一个不存在的 plugin 或 preset，可能是名称拼写错误，或是未正确安装。

```ts
// 错误示例
pluginBabel({
  babelLoaderOptions: (config, { addPlugins }) => {
    // 该插件名称错误，或者未安装
    addPlugins('babel-plugin-not-exists');
  },
});
```

2. 是否配置了多个 babel-plugin-import，但是没有在数组的第三项声明每一个 babel-plugin-import 的名称。

```ts
// 错误示例
pluginBabel({
  babelLoaderOptions: (config, { addPlugins }) => {
    addPlugins([
      ['babel-plugin-import', { libraryName: 'antd', libraryDirectory: 'es' }],
      [
        'babel-plugin-import',
        { libraryName: 'antd-mobile', libraryDirectory: 'es' },
      ],
    ]);
  },
});
```

```ts
// 正确示例
pluginBabel({
  babelLoaderOptions: (config, { addPlugins }) => {
    addPlugins([
      [
        'babel-plugin-import',
        { libraryName: 'antd', libraryDirectory: 'es' },
        'antd',
      ],
      [
        'babel-plugin-import',
        { libraryName: 'antd-mobile', libraryDirectory: 'es' },
        'antd-mobile',
      ],
    ]);
  },
});
```
